/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   dsstream.c
 *  Content:   Illustrates streaming data from a disk WAVE file to a
 *             DirectSound secondary buffer for playback.
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <commctrl.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>

#include "dsstream.h"
#include "wassert.h"

char szAppClass[] = "DSStreamWndClass";
char szAppName[]  = "DSStream";

char szAppTitle[64];
char szAppCaption[64];
char szPan[32];
char szVolume[32];
char szFrequency[32];
char szProgress[32];
char szOpenFilter[128];
char szOpenDLGTitle[64];


char szTemp[256];
char szDebug[128];
char szFileBuffer[MAX_PATH];
char szFileTitle[MAX_PATH];
char szCDStartPath[MAX_PATH];           // The path to start the Open dialog in
 
// Registry Key and Value names that allow us to retrive a path something like
// "C:\DXSDK\SDK\MEDIA", but matching the current install.
static const TCHAR gszRegKeyDirect3D[] = TEXT("Software\\Microsoft\\Direct3D");
static const TCHAR gszRegValueD3DPath[] = TEXT("D3D Path");

// Size of longest token below
#define MAX_TOKEN_LEN   7


static TCHAR gszPlayToken[] = TEXT("PLAY");
static TCHAR gszLoopToken[] = TEXT("LOOP");
static TCHAR gszCloseToken[] = TEXT("CLOSE");
static TCHAR gszStickyToken[] = TEXT("STICKY");
static TCHAR gszGlobalToken[] = TEXT("GLOBAL");

LPDIRECTSOUND           lpDS = NULL;
LPDIRECTSOUNDBUFFER     lpDSBStreamBuffer = NULL;

WAVEINFOCA              wiWave;

HWND    hWndMain, hWndPan, hWndPanText, hWndVol, hWndVolText, hWndFreqText;
HWND    hWndBar, hWndPlay, hWndStop, hWndLoopCheck, hWndFreq, hWndProg;
HWND    hWndProgText;

#ifdef DEBUG
HWND                    hWndList;
#endif

HINSTANCE       hInst;

static BOOL     bFileOpen = FALSE, bPlaying = FALSE, bTimerInstalled = FALSE;
static BOOL     bEnumDrivers = FALSE, gfCloseOnDone = FALSE;
static UINT     uTimerID = 0, uLastPercent = 100;
static GUID     guID;
static DWORD    gdwFocus = 0;

static BOOL InitApp( HINSTANCE );
static BOOL InitInstance( HINSTANCE, int );
static void BuildTitleBarText( void );
// Call this in initialization to set the szCDStartPath[] global variable
static void GetMediaStartPath( void );
static void FillDataBuffer( void );
static void LoadFromCommandLine( LPSTR lpszCmd );

/******************************************************************************
 * WinMain()
 *
 * Entry point for all Windows programs - performs initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                        LPSTR lpCmdLine, int nCmdShow )
    {
    MSG     msg;

    hInst = hInstance;

    /* Make sure the common controls are loaded for our use */
    InitCommonControls();


    if( !hPrevInstance )
        if( !InitApp( hInstance ))
            {
            ErrorMessageBox( IDS_ERROR_APPINIT, MB_ICONSTOP );
            return( FALSE );
            }

    if( !InitInstance( hInstance, nCmdShow ))
        {
        ErrorMessageBox( IDS_ERROR_INSTANCEINIT, MB_ICONSTOP );
        return( FALSE );
        }

    // We know how to take exactly one file name from the command-line and open
    // it as a file to play.  We can also accept a couple command like /play,
    // /close, and /loop
    if( lpCmdLine[0] )
        LoadFromCommandLine( lpCmdLine );

    while( GetMessage((LPMSG)&msg, NULL, 0, 0 ))
        {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
        }

    UnregisterClass( szAppClass, hInstance );
    return( msg.wParam );
    } /* End of WinMain() */


/*****************************************************************************/
/* InitApp()                                                                 */
/*                                                                           */
/*   Inits things that only need to be created once for the this application */
/* (like creating the window class).                                         */
/*****************************************************************************/
static BOOL InitApp( HINSTANCE hInstance )
    {
    WNDCLASS    wc;

    /* Set up and register a window class */
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName    = szAppClass;
    wc.lpfnWndProc      = (WNDPROC)MainWindowProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = sizeof( DWORD );
    wc.hInstance        = hInstance;
    wc.hIcon            = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON1 ));
    wc.hCursor          = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground    = (HBRUSH)(COLOR_WINDOW);
    wc.lpszMenuName     = MAKEINTRESOURCE( IDR_MAINMENU );

    if( !RegisterClass( &wc ))
        {
        ErrorMessageBox( IDS_ERROR_REGISTERCLASS, MB_ICONSTOP );
        return( FALSE );
        }
    return( TRUE );
    } /* End of InitApp() */


/*****************************************************************************/
/* InitInstance()                                                            */
/*                                                                           */
/* Performs initialization that must be done for each application instance.  */
/*                                                                           */
/*****************************************************************************/
static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
    {
    HWND        hWnd;
    HRESULT     dsRetVal;
    RECT        crect;
    int         cx, cy;
    UINT        uCharsRead;
    BOOL        fUseGuid = FALSE;
    HMENU       hSysMenu;

    DbgInitialize( TRUE );

    // Initialize the Media start path for common open boxes
    GetMediaStartPath();
 
    LoadString( hInstance, IDS_APP_TITLE, szAppTitle, sizeof(szAppTitle));
    LoadString( hInstance, IDS_APP_CAPTION, szAppCaption, sizeof(szAppCaption));
    LoadString( hInstance, IDS_TBTITLE_PAN, szPan, sizeof(szPan));
    LoadString( hInstance, IDS_TBTITLE_VOLUME, szVolume, sizeof(szVolume));
    LoadString( hInstance, IDS_TBTITLE_FREQUENCY,
                                            szFrequency, sizeof(szFrequency));
    LoadString( hInstance, IDS_TBTITLE_PROGRESS,
                                            szProgress, sizeof(szProgress));
    LoadString( hInstance, IDS_OPEN_DLGTITLE,
                                        szOpenDLGTitle, sizeof(szOpenDLGTitle));
/* This is a little trick designed to allow us to load a common dialog box
 * filter string, which is really a concatentation of several NULL-terminated
 * strings. Note that while is is possible to enter something else into the
 * resource as placeholders for the NULL characters, this has the undesireable
 * effect of forcing us to search-and-replace byte-by-byte and doesn't make it
 * as easy to internationalize our strings...
 */
    memset( szOpenFilter, 0, sizeof(szOpenFilter));
    uCharsRead = LoadString( hInstance, IDS_OPEN_FILTER1,
                                szOpenFilter, sizeof(szOpenFilter)) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER2,
                                &szOpenFilter[uCharsRead],
                                sizeof(szOpenFilter) - uCharsRead ) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER3,
                                &szOpenFilter[uCharsRead],
                                sizeof(szOpenFilter) - uCharsRead ) + 1;
    LoadString( hInstance, IDS_OPEN_FILTER4,
                                &szOpenFilter[uCharsRead],
                                sizeof(szOpenFilter) - uCharsRead );

    /* Calculate the size of the client window */
    cx = CONTROL_SPACE_CX + 2*BORDER_SPACE_CX + BUTTON_CX + PAN_TB_CX
        + 2*GetSystemMetrics( SM_CXBORDER ) + PAN_TEXT_CX + TEXT_SPACE_CX;

    cy = 2*(BORDER_SPACE_CY + GetSystemMetrics( SM_CYBORDER ))
        + PAN_TB_CY + VOL_TB_CY + FREQ_TB_CY + PROG_TB_CY
        + GetSystemMetrics( SM_CYMENU ) + 3*CONTROL_SPACE_CY
        + GetSystemMetrics( SM_CYCAPTION );

    /* Create an application window */
#ifdef DEBUG
    hWnd = CreateWindow( szAppClass,            /* class name */
                        szAppCaption,           /* caption for window */
                        WS_OVERLAPPEDWINDOW     /* style */
                         & ~WS_THICKFRAME | WS_BORDER,
                        CW_USEDEFAULT,          /* x position */
                        CW_USEDEFAULT,          /* y position */
                        cx,                     /* width */
                        cy + 200,               /* height */
                        NULL,                   /* parent window */
                        NULL,                   /* menu */
                        hInstance,              /* instance */
                        NULL );                 /* parms */
#else
    hWnd = CreateWindow( szAppClass,            /* class name */
                        szAppCaption,           /* caption for window */
                        WS_OVERLAPPEDWINDOW     /* style */
                         & ~WS_THICKFRAME | WS_BORDER,
                        CW_USEDEFAULT,          /* x position */
                        CW_USEDEFAULT,          /* y position */
                        cx,                     /* width */
                        cy,                     /* height */
                        NULL,                   /* parent window */
                        NULL,                   /* menu */
                        hInstance,              /* instance */
                        NULL );                 /* parms */
#endif

    if( !hWnd )
        {
        ErrorMessageBox( IDS_ERROR_MAINWNDCREATE, MB_ICONSTOP );
        return( FALSE );
        }

    hWndMain = hWnd;
    GetClientRect( hWndMain, &crect );

    // Apparently WM_INITMENU isn't called for the main window's context version
    // of the system menu, so we must gray these at startup as well.
    hSysMenu = GetSystemMenu( hWndMain, FALSE );
    EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );
    EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );

#ifdef DEBUG
    cy = 2*BORDER_SPACE_CY + PAN_TB_CY + VOL_TB_CY + FREQ_TB_CY + PROG_TB_CY
                + 3*CONTROL_SPACE_CY;

    hWndList = CreateWindow( "listbox", NULL, WS_CHILD | WS_VISIBLE
                                | LBS_NOINTEGRALHEIGHT | WS_VSCROLL,
                                0, cy, crect.right-crect.left,
                                crect.bottom - crect.top - cy,
                                hWnd, NULL, hInstance, NULL );
#endif

    /* Create some controls for things like volume, panning, etc. */
    if( CreateChildren( crect ))
        return( FALSE );

    ShowWindow( hWnd, nCmdShow );
    UpdateWindow( hWnd );

    /* Create the main DirectSound object */

    bEnumDrivers = GetProfileInt( "DSSTREAM", "EnumDrivers", FALSE );
    if( bEnumDrivers && !DoDSoundEnumerate( &guID ))
        {
        fUseGuid = TRUE;
        }
    dsRetVal = DirectSoundCreate( fUseGuid ? &guID : NULL, &lpDS, NULL );

    
    if( dsRetVal != DS_OK )
        {
        ErrorMessageBox( IDS_ERROR_DSCREATE, MB_ICONSTOP );
        return( FALSE );
        }

    dsRetVal = lpDS->lpVtbl->SetCooperativeLevel( lpDS,
                                                hWndMain,
                                                DSSCL_NORMAL );
    if( dsRetVal != DS_OK )
        {
        ErrorMessageBox( IDS_ERROR_DSCOOPERATIVE, MB_ICONSTOP );
        return( FALSE );
        }


    return( TRUE );
    } /* End of InitInstance() */


/****************************************************************************/
/* MainWindowProc()                                                         */
/*                                                                          */
/*    Messages for our main window are handled here                         */
/*                                                                          */
/****************************************************************************/
extern LONG lInTimer;    
LRESULT CALLBACK MainWindowProc( HWND hWnd, unsigned uMsg,
                                                WPARAM wParam, LPARAM lParam )
    {
#ifndef DEBUG
    LPMINMAXINFO lpMinMax;
#endif
    DWORD   dwCDErr = 0, dwProg;
    float   fPercent;
    UINT    uPercent;
    BOOL    bResult = FALSE;
    int     nChkErr;
    HRESULT dsrval;

    switch( uMsg )
        {
        case WM_DSSTREAM_PROGRESS:
            dwProg = (DWORD)lParam;
            dwProg  = dwProg % wiWave.mmckInRIFF.cksize;
            fPercent = (float)((dwProg * 100)
                                        / wiWave.mmckInRIFF.cksize);
            SendMessage( hWndProg, TBM_SETPOS,
                        TRUE, (DWORD)(float)(fPercent*(float)PROG_MULTIPLIER));
            uPercent = (UINT)fPercent;
            if( uPercent != uLastPercent )
                {
                uLastPercent = uPercent;
                wsprintf( szTemp, "%s: %u%%", szProgress, uPercent );
                Static_SetText( hWndProgText, szTemp );
#ifdef DEBUG
                ListBox_AddString( hWndList, szTemp );
#endif
                }
            break;

        /*
         *      This message will be posted by the helper DLL when the TimeFunc
         * is done streaming the WAVE file. It serves as notification that the
         * caller should terminate WAVE playback and end the MM timer event.
         */
        case WM_DSSTREAM_DONE:
            /* Emulate a WM_COMMAND to ourselves */
            SendMessage( hWnd, WM_COMMAND, MAKEWPARAM( IDM_STOP, 0 ), 0L );
            break;

#ifdef DEBUG
        case WM_DSSTREAM_DEBUG:
            if( LOWORD(wParam) == DEBUGF_PLAYPOSITION )
                {
                wsprintf( szDebug, "pp = %li", lParam );
                ListBox_AddString( hWndList, szDebug );
                DPF( 4, szDebug );
                }
            else if( LOWORD(wParam) == DEBUGF_WRITEPOSITION )
                {
                wsprintf( szDebug, "wp = %li", lParam );
                ListBox_AddString( hWndList, szDebug );
                DPF( 4, szDebug );
                }
            else if( LOWORD(wParam) == DEBUGF_NEXTWRITE )
                {
                wsprintf( szDebug, "nw = %li", lParam );
                ListBox_AddString( hWndList, szDebug );
                DPF( 4, szDebug );
                }
            else if( LOWORD(wParam) == DEBUGF_SKIP )
                {
                ListBox_AddString( hWndList, "Skipped segment read" );
                DPF( 5, szDebug );
                }
            break;
#endif

        case WM_COMMAND:
            switch( LOWORD( wParam ))
                {
                case IDM_FILE_OPEN:
                    {
                    OPENFILENAME        ofn;
    /*
     * Clear out and fill in an OPENFILENAME structure in preparation
     * for creating a common dialog box to open a file.
     */
                    memset( &ofn, 0, sizeof(OPENFILENAME));
                    ofn.lStructSize     = sizeof(OPENFILENAME);
                    ofn.hwndOwner       = hWnd;
                    ofn.hInstance       = hInst;
                    ofn.lpstrFilter     = szOpenFilter;
                    ofn.nFilterIndex    = 1;
                    szFileBuffer[0]     = '\0';
                    ofn.lpstrFile       = szFileBuffer;
                    ofn.nMaxFile        = sizeof(szFileBuffer);
                    ofn.lpstrFileTitle  = szFileTitle;
                    ofn.nMaxFileTitle   = sizeof(szFileTitle);
                    ofn.lpstrInitialDir = szCDStartPath;
                    ofn.lpstrDefExt     = "WAV";
                    ofn.lpstrTitle      = szOpenDLGTitle;
                    ofn.Flags           = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

                    bResult = GetOpenFileName( &ofn ); /* Do the dialog box */

    /*
     *  A return of TRUE indicates that the user did not select a filename.
     * The possible reasons are: Cancel was clicked, or an error occured.
     * If Cancel was clicked, the CommDlgExtendedError() function will not
     * return a valid code.  For anything else, an error code will come back.
     */
                    if( bResult == FALSE )
                        {
                        dwCDErr = CommDlgExtendedError();
                        if( dwCDErr )
                            {
                            /* Handle a common dialog box error */
                            HandleCommDlgError( dwCDErr );
                            }
                        else    /* Clicked Cancel, so finish msg processing */
                            return( 0 );
                        }
                    else
                        {
                        // Copy the directory name we opened from so that we start there
                        // next time we open the dialog box...
                        lstrcpy( szCDStartPath, szFileBuffer );
                        szCDStartPath[ofn.nFileOffset] = '\0';

                        if( bFileOpen )
                            {
                            // Before we force a close, disable auto close
                            // because the user has picked a new file and is
                            // obviously not ready for us to go away yet.
                            gfCloseOnDone = FALSE;
    /* Need to close the previous file before we open a new one.  The best
     * way to do this is by faking a menu command, so that we only have the
     * actual code in one place and it can easily be changed.
     */
                            SendMessage( hWnd, WM_COMMAND,
                                        MAKEWPARAM( IDM_FILE_CLOSE, 0 ), 0L );
                            }
                                        
                        if(( nChkErr = StreamBufferSetup()) != 0 )
                            {
                            // Error opening the WAVE file so abort
                            break;
                            }
                        else
                            {
                            bFileOpen = TRUE;
                            EnableMenuItem( GetMenu( hWnd ), IDM_PLAY,
                                                MF_BYCOMMAND | MF_ENABLED );
                            EnableWindow( hWndPlay, TRUE );
                            EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
                                                MF_BYCOMMAND | MF_ENABLED );
                            DrawMenuBar( hWnd );
                            BuildTitleBarText();
                            }
                        }
                    }
                    break;

                case IDM_FILE_CLOSE:
                    SendMessage( hWnd, WM_COMMAND,
                                MAKEWPARAM( IDM_STOP,
                                            DSSTREAM_STOPF_NOREOPEN ), 0L );
                    BuildTitleBarText();
                    break;

                case IDM_OPTIONS_ENUMDRIVERS:
                    bEnumDrivers = !bEnumDrivers;
                    if( bEnumDrivers )
                        {
                        LoadString( hInst, IDS_ENUMWARNING, szTemp, sizeof(szTemp));
                        MessageBox( hWnd, szTemp, szAppCaption, MB_OK );
                        }
                    break;

                case IDM_HELP_ABOUT:
                    DialogBox( hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWndMain,
                                (DLGPROC)DLG_About );
                    break;

                case IDC_LOOPCHECK:
                    wiWave.bLoopFile = !wiWave.bLoopFile;
                    Button_SetCheck( hWndLoopCheck, wiWave.bLoopFile );
                    if( !bPlaying && bFileOpen )
                        ResetWavePlayer();
                    break;

                case IDM_PLAY:
#ifdef DEBUG
                    wsprintf( szDebug, "  bFileOpen = %s",
                                bFileOpen == TRUE ? "TRUE" : "FALSE" );
                    DPF( 3, "IDM_PLAY Debug" );
                    DPF( 3, szDebug );
                    wsprintf( szDebug, "  bTimerInstalled = %s",
                                bTimerInstalled == TRUE ? "TRUE" : "FALSE" );
                    DPF( 3, szDebug );
                    wsprintf( szDebug, "  bPlaying = %s",
                                bPlaying == TRUE ? "TRUE" : "FALSE" );
                    DPF( 3, szDebug );
#endif
                    if( bPlaying )
                        {
                        gfCloseOnDone = FALSE;
                        SendMessage( hWnd, WM_COMMAND,
                                        MAKEWPARAM( IDM_STOP, 0 ), 0L );
                        }
                    if( bFileOpen && lpDSBStreamBuffer )
                        {
                        // Ensure that position is at 0, ready to go
                        dsrval = lpDSBStreamBuffer->lpVtbl->SetCurrentPosition(
                                lpDSBStreamBuffer,
                                0 );
                        dsrval = lpDSBStreamBuffer->lpVtbl->Play( lpDSBStreamBuffer,
                                                      0, 0, DSBPLAY_LOOPING );
                        }
                    else
                        {
                        bPlaying = bTimerInstalled = FALSE;
                        break;
                        }

                    if( timeBeginPeriod( PLAYBACK_TIMER_PERIOD
                                            / PLAYBACK_OVERSAMPLE ) != 0 )
                        {
                        /* Can't create timer! */
                        dsrval = lpDSBStreamBuffer->lpVtbl->Stop( lpDSBStreamBuffer );
                        bPlaying = bTimerInstalled = FALSE;
                        break;
                        }
                    else
                        {
                            lInTimer = FALSE;
                            if(( uTimerID = timeSetEvent( PLAYBACK_TIMER_PERIOD
                                                           / PLAYBACK_OVERSAMPLE,
                                                          PLAYBACK_TIMER_ACCURACY,
                                                          TimeFunc, (DWORD)0,
                                                          TIME_PERIODIC )) != 0 )
                            bTimerInstalled = TRUE;
                        }
                    bPlaying = TRUE;
                    EnableMenuItem( GetMenu( hWnd ), IDM_STOP,
                                        MF_BYCOMMAND | MF_ENABLED );
                    EnableWindow( hWndStop, TRUE );
                    DrawMenuBar( hWnd );
                    break;

                case IDM_STOP:
#ifdef DEBUG
                    wsprintf( szDebug, "  bFileOpen = %s",
                                    bFileOpen == TRUE ? "TRUE" : "FALSE" );
                    DPF( 3, "IDM_STOP Debug" );
                    DPF( 3, szDebug );
                    wsprintf( szDebug, "  bTimerInstalled = %s",
                                bTimerInstalled == TRUE ? "TRUE" : "FALSE" );
                    DPF( 3, szDebug );
                    wsprintf( szDebug, "  bPlaying = %s",
                                bPlaying == TRUE ? "TRUE" : "FALSE" );
                    DPF( 3, szDebug );
#endif
                    wiWave.bDonePlaying = TRUE;
                    if( bTimerInstalled )
                    {
                        timeKillEvent( uTimerID );
                        timeEndPeriod( PLAYBACK_TIMER_PERIOD / PLAYBACK_OVERSAMPLE );
                        // Busy wait for timer func to exit
                        while (InterlockedExchange(&lInTimer, TRUE)) Sleep(100);
                        bTimerInstalled = FALSE;
                    }
                    if( bPlaying )
                        {
                        bPlaying = FALSE;
                        dsrval = lpDSBStreamBuffer->lpVtbl->Stop( lpDSBStreamBuffer );
                        EnableMenuItem( GetMenu( hWnd ), IDM_STOP,
                                            MF_BYCOMMAND | MF_GRAYED );
                        EnableWindow( hWndStop, FALSE );
                        DrawMenuBar( hWnd );
                        }
                    // Short circuit to allow command-line forced shutdown
                    if(!( HIWORD(wParam) & DSSTREAM_STOPF_NOREOPEN )
                                                        && !gfCloseOnDone )
                        {
                        ResetWavePlayer();
                        break;
                        }
                    else
                        {
                        if( bFileOpen )
                            {
                            WaveCloseReadFile( &wiWave.hmmio,
                                                            &wiWave.pwfx );
                            if( lpDSBStreamBuffer )
                                dsrval = lpDSBStreamBuffer->lpVtbl->Release(
                                                           lpDSBStreamBuffer );

                            lpDSBStreamBuffer = NULL;
                            
                            bFileOpen = FALSE;
                            // The file is closed, so disable the close option
                            EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
                                            MF_BYCOMMAND | MF_GRAYED );
                            EnableMenuItem( GetMenu( hWnd ), IDM_PLAY,
                                            MF_BYCOMMAND | MF_GRAYED );
                            EnableWindow( hWndPlay, FALSE );
                            PostMessage( hWnd, WM_DSSTREAM_PROGRESS, 0L, 0L );
                            if( gfCloseOnDone &&
                                    !(HIWORD(wParam) && DSSTREAM_STOPF_NOEXIT))
                                SendMessage( hWnd, WM_COMMAND,
                                        MAKEWPARAM( IDM_FILE_EXIT, 0 ), 0L );
                            }
                        }
                    break;

                case IDM_FILE_EXIT:
                    DestroyWindow( hWnd );
                    break;
                }
            break;
#ifndef DEBUG
        case WM_GETMINMAXINFO:
    /*
     * We know exactly how big this window should be, and it's sort of a
     * little pop-up control panel, so we can disable window sizing by
     * forcing all the minimum and maximum sizes to be the calculated size.
     */
            lpMinMax = (LPMINMAXINFO)lParam;

            lpMinMax->ptMaxSize.x = CONTROL_SPACE_CX + 2*BORDER_SPACE_CX
                                    + BUTTON_CX + PAN_TB_CX + PAN_TEXT_CX
                                    + TEXT_SPACE_CX
                                    + 2*GetSystemMetrics( SM_CXBORDER );
            lpMinMax->ptMaxSize.y = 2*(BORDER_SPACE_CY
                                    + GetSystemMetrics( SM_CYBORDER ))
                                    + PAN_TB_CY + VOL_TB_CY + FREQ_TB_CY
                                    + PROG_TB_CY + 3*CONTROL_SPACE_CY
                                    + GetSystemMetrics( SM_CYMENU )
                                    + GetSystemMetrics( SM_CYCAPTION );

            lpMinMax->ptMinTrackSize.x = lpMinMax->ptMaxTrackSize.x
                                                    = lpMinMax->ptMaxSize.x;

            lpMinMax->ptMinTrackSize.y = lpMinMax->ptMaxTrackSize.y
                                                    = lpMinMax->ptMaxSize.y;
            break;
#endif
        case WM_HSCROLL:
            if(((HWND)lParam == hWndPan) && lpDSBStreamBuffer )
                {
                HandlePanScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            else if(((HWND)lParam == hWndVol) && lpDSBStreamBuffer )
                {
                HandleVolScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            else if(((HWND)lParam == hWndFreq) && lpDSBStreamBuffer )
                {
                HandleFreqScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            break;

        case WM_INITMENU:
            {
            HMENU       hSysMenu = GetSystemMenu( hWnd, FALSE );

            EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );
            EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );

            if((HMENU)wParam != GetMenu( hWnd ))
                break;
            CheckMenuItem((HMENU)wParam, IDM_OPTIONS_ENUMDRIVERS,
                                bEnumDrivers ? MF_CHECKED : MF_UNCHECKED );
            }
            break;

        case WM_DESTROY:
    /*
     * Free all the DirectSound objects we created
     */
            SendMessage( hWnd, WM_COMMAND,
                          MAKEWPARAM( IDM_STOP, DSSTREAM_STOPF_NOREOPEN
                                                | DSSTREAM_STOPF_NOEXIT ), 0 );

            if( bTimerInstalled )
            {
                timeKillEvent( uTimerID );
                timeEndPeriod( PLAYBACK_TIMER_PERIOD / PLAYBACK_OVERSAMPLE );
                // Busy wait for timer func to exit
                while (InterlockedExchange(&lInTimer, TRUE)) Sleep(100);
                bTimerInstalled = FALSE;
            }

            if( lpDS ) dsrval = lpDS->lpVtbl->Release( lpDS );

            WriteProfileString( "DSSTREAM", "EnumDrivers",
                                        bEnumDrivers ? "1" : "0" );

            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
        }
    return 0L;
    } /* WindowProc */


/*****************************************************************************/
/* DLG_About()                                                               */
/*                                                                           */
/*   Dialog procedure for the Help...About... box which simply pops up a     */
/* little copyright message and brief program description.                   */
/*                                                                           */
/*****************************************************************************/
BOOL CALLBACK DLG_About( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
    {
    switch( msg )
        {
        case WM_INITDIALOG:
            break;

        case WM_COMMAND:
            switch( LOWORD(wParam))
                {
                case IDOK:
                    EndDialog( hDlg, FALSE );
                    return( TRUE );

                default:
                    break;
                }
            break;

        default:
            return( FALSE );
        }

    return( FALSE );
    }


/*****************************************************************************/
/* HandleCommDlgError()                                                      */
/*                                                                           */
/*    The function translates extended common dialog error codes into a      */
/* string resource ID, loads that string from our module, and displays it in */
/* a message box. This implementation only covers the general CD error codes.*/
/*                                                                           */
/*****************************************************************************/
int HandleCommDlgError( DWORD dwError )
    {
    char szTitle[128];
    UINT uMsgID;

    if( dwError == CDERR_DIALOGFAILURE )
        uMsgID = IDS_CDERR_DIALOGFAILURE;
    else
        uMsgID = (UINT)dwError + IDS_CDERR_GENERAL_BASE;

    LoadString( hInst, uMsgID, szTemp, sizeof(szTemp));
    LoadString( hInst, IDS_CDERR_TITLESTRING, szTitle, sizeof(szTitle));
    MessageBox( GetActiveWindow(), szTemp, szTitle,
                    MB_OK | MB_ICONEXCLAMATION );
                
    return( 0 );
    }


/*****************************************************************************/
/* StreamBufferSetup()                                                       */
/*                                                                           */
/*    This function uses the filename stored in the global character array to*/
/* open a WAVE file. Then it creates a secondary DirectSoundBuffer object    */
/* which will later be used to stream that file from disk during playback.   */
/*                                                                           */
/*****************************************************************************/
int StreamBufferSetup( void )
    {
    DSBUFFERDESC dsbd;
    HRESULT      dsRetVal;
        
    int nChkErr;

    /* This portion of the WAVE I/O is patterned after what's in DSTRWAVE, which
     * was in turn adopted from WAVE.C which is part of the DSSHOW sample.
     */

    if(( nChkErr = WaveOpenFile( szFileBuffer, &wiWave.hmmio, &wiWave.pwfx, &wiWave.mmckInRIFF )) != 0 )
        {
        ErrorMessageBox( IDS_ERROR_WAVEFILEOPEN, MB_ICONEXCLAMATION );
        return( ERR_WAVE_OPEN_FAILED );
        }

    if( wiWave.pwfx->wFormatTag != WAVE_FORMAT_PCM )
        {
        ErrorMessageBox( IDS_ERROR_WAVENOTPCM, MB_ICONEXCLAMATION );
        WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
        return( ERR_WAVE_INVALID_FORMAT );
                }
    /* Seek to the data chunk */
    if(( nChkErr = WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF )) != 0 )
        {
        ErrorMessageBox( IDS_ERROR_WAVESEEKFAILED, MB_ICONEXCLAMATION );
        WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
        return( ERR_WAVE_CORRUPTED_FILE );
        }
    /* As a side note, mmck.ckSize will be the size of all the data in this file.
     * That's something which might be handy when calculating the length... */

    /* Calculate a buffer length, making sure it is an exact multiple of the
     * buffer segment size.
     */
    wiWave.dwBufferSize = ((DWORD)wiWave.pwfx->nAvgBytesPerSec
                            * (((NUM_BUFFER_SEGMENTS * PLAYBACK_TIMER_PERIOD)
                            / 10)) / 100);

#ifdef DEBUG
    wsprintf( szDebug, "BufferSize = %lu", wiWave.dwBufferSize );
    ListBox_AddString( hWndList, szDebug );
#endif
    /*
     * Create the secondary DirectSoundBuffer object to receive our sound data.
     */
    memset( &dsbd, 0, sizeof( DSBUFFERDESC ));
    dsbd.dwSize = sizeof( DSBUFFERDESC );
    // Use new GetCurrentPosition() accuracy (DirectX 2 feature)
    dsbd.dwFlags = DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2 | gdwFocus;
    dsbd.dwBufferBytes = wiWave.dwBufferSize;

    /* Set Format properties according to the WAVE file we just opened */
    dsbd.lpwfxFormat = wiWave.pwfx;
    dsRetVal = lpDS->lpVtbl->CreateSoundBuffer( lpDS,
                                                &dsbd,
                                                &lpDSBStreamBuffer,
                                                NULL );
    if( dsRetVal != DS_OK )
        {
        ErrorMessageBox( IDS_ERROR_DSBCREATE, MB_ICONEXCLAMATION );
        return( ERR_CREATEDSB_FAILED );
        }

    wiWave.lpDSBStreamBuffer = lpDSBStreamBuffer;
    wiWave.bFoundEnd = FALSE;
    wiWave.dwBytesRemaining = 0;
    wiWave.bLoopFile = Button_GetCheck( hWndLoopCheck );

    FillDataBuffer();

    wiWave.bDonePlaying = FALSE;

#ifdef DEBUG
    wsprintf( szDebug, "wiWave.dwBufferSize = %lu", wiWave.dwBufferSize );
    DPF( 3, "StreamBufferSetup Debug" );
    DPF( 3, szDebug );
#endif

    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_MAX );
    SendMessage( hWndPan, TBM_SETPOS, TRUE, PAN_CENTER );
    SendMessage( hWndFreq, TBM_SETPOS, TRUE,
                    (LPARAM)wiWave.pwfx->nSamplesPerSec / FREQ_MULTIPLIER );
    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, 0L );
    UpdateFromControls();
    return( 0 );
    }


/*****************************************************************************/
/* ResetWavePlayer()                                                         */
/*                                                                           */
/*  Performs a subset of the above operations (in StreamBufferSetup). Things */
/* not done include creating a DSB and opening the file (it's already open). */
/*                                                                           */
/*****************************************************************************/
void ResetWavePlayer( void )
    {
    WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF );
    wiWave.bFoundEnd = FALSE;
    wiWave.dwBytesRemaining = 0;

    FillDataBuffer();

    wiWave.bDonePlaying = FALSE;
    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, 0L );
    }


/*****************************************************************************/
/* FillDataBuffer()                                                          */
/*                                                                           */
/*   This function fills the sound buffer with a block of data, starting at  */
/* the current read position.  The entire buffer is filled.                  */
/*                                                                           */
/*****************************************************************************/
void FillDataBuffer( void )
    {
    LPBYTE      lpWrite1, lpWrite2;
    DWORD       dwLen1, dwLen2;
    UINT        uActualBytesWritten;
    int         nChkErr;
    HRESULT     dsRetVal;

    dsRetVal = lpDSBStreamBuffer->lpVtbl->Lock( lpDSBStreamBuffer,
                                        0, wiWave.dwBufferSize,
                                        &((LPVOID)lpWrite1), &dwLen1,
                                        &((LPVOID)lpWrite2), &dwLen2,
                                        0 );
    if( dsRetVal != DS_OK )
        {
        return;
        //ErrorMessageBox( IDS_ERROR_DSBLOCK, MB_EXLCAMATION );
        }

    Assert( NULL == lpWrite2 );
    Assert( 0 == dwLen2 );

    if( dwLen1 )
        {
        Assert( NULL != lpWrite1 );

        nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLen1, lpWrite1,
                                &wiWave.mmck, &uActualBytesWritten );
        if( uActualBytesWritten < dwLen1 )
            {
            if( wiWave.bLoopFile )
                {
    /* If the file is shorter than the buffer and we're looping, we need to
     * read the file in again so that we don't get a block of silence before
     * the timer loops playback.
     */
                LPBYTE lpTemp = lpWrite1;
                DWORD   cbReadLoopTotal = dwLen1;

                do
                    {
                    /* Continue decrementing our count and moving our temp
                     * pointer forward until we've read the file enough times
                     * to fill the buffer.  NOTE: It's probably not efficient
                     * to bother with the overhead of streaming a file that's
                     * not at least as large as the buffer... */
                    lpTemp += uActualBytesWritten;
                    cbReadLoopTotal -= uActualBytesWritten;
                    nChkErr = WaveStartDataRead( &wiWave.hmmio,
                                                    &wiWave.mmck,
                                                    &wiWave.mmckInRIFF );
                    nChkErr = WaveReadFile( wiWave.hmmio, (UINT)cbReadLoopTotal,
                                            lpTemp,
                                            &wiWave.mmck, &uActualBytesWritten );
                    } while( uActualBytesWritten < cbReadLoopTotal );
                }
            else
                {
                wiWave.bFoundEnd = TRUE;
                wiWave.dwBytesRemaining = (DWORD)uActualBytesWritten;
                DPF( 3,"Setting bFoundEnd in load" );

                // Fill in silence
                FillMemory( lpWrite1+uActualBytesWritten,
                            dwLen1-uActualBytesWritten,
                            (BYTE)(wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0 ));
                }
            }
        }
    dsRetVal = lpDSBStreamBuffer->lpVtbl->Unlock( lpDSBStreamBuffer,
                                        (LPVOID)lpWrite1, dwLen1,
                                        (LPVOID)lpWrite2, 0 );

    wiWave.dwNextWriteOffset = wiWave.dwProgress = 0;
    }


/*****************************************************************************/
/* CreateChildren()                                                          */
/*                                                                           */
/*   This function creates a bunch of child controls for the main window.    */
/* Most of them are used for controling various things about a playing sound */
/* file, like volume and panning. Returns FALSE if no errors, TRUE otherwise.*/
/*                                                                           */
/*****************************************************************************/
int CreateChildren( RECT crect )
    {
    SIZE  Size;
    HDC   hDC;
    int   x, y;
    UINT  uType;
    char  szTemplate[128], szType[32];
    LPSTR lpszControl;

    LoadString( hInst, IDS_ERROR_CHILDTEMPLATE, szTemplate, sizeof(szTemplate));

    /* Don't handle failure for this one, because the app will still run fine */
    hWndBar = CreateWindow( "static", NULL,
                            WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
                            0, 0, crect.right, 2, hWndMain,
                            (HMENU)0, hInst, NULL );

    hDC = GetDC( hWndMain );
    if( !GetTextExtentPoint32( hDC, szPan, strlen(szPan), &Size ))
        {
        ErrorMessageBox( IDS_ERROR_GETTEXTEXTENT, MB_ICONEXCLAMATION );
        ReleaseDC( hWndMain, hDC );
        return( TRUE );
        }
    ReleaseDC( hWndMain, hDC );

    y = BORDER_SPACE_CY;

    /* STATIC control -- text label for the pan trackbar */
    if(( hWndPanText = CreateWindow( "static", szPan,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
                                    BORDER_SPACE_CX + PAN_TB_CX + TEXT_SPACE_CX,
                                    y + (PAN_TB_CY - Size.cy)/2,
                                    PAN_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szPan;
        uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
        }

    /* PAN (left to right balance) trackbar control */
    if(( hWndPan = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
                                BORDER_SPACE_CX,
                                y, PAN_TB_CX, PAN_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szPan;
        uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
        }

    SendMessage( hWndPan, TBM_SETRANGE, FALSE, MAKELONG( PAN_MIN, PAN_MAX )); 
    SendMessage( hWndPan, TBM_SETPOS, TRUE, PAN_CENTER );
    SendMessage( hWndPan, TBM_SETPAGESIZE, 0L, PAN_PAGESIZE );

    y += PAN_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the volume trackbar */
    if(( hWndVolText = CreateWindow( "static", szVolume,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
                                    BORDER_SPACE_CX + VOL_TB_CX + TEXT_SPACE_CX,
                                    y + (VOL_TB_CY - Size.cy)/2,
                                    VOL_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szVolume;
        uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
        }

    /* Create the VOLUME trackbar */
    if(( hWndVol = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
                                BORDER_SPACE_CX,
                                y, VOL_TB_CX, VOL_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szVolume;
        uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
        }

    SendMessage( hWndVol, TBM_SETRANGE, FALSE,
                                        MAKELONG( VOL_MIN, VOL_MAX ));
    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_MAX );
    SendMessage( hWndVol, TBM_SETPAGESIZE, 0L, VOL_PAGESIZE );

    y += VOL_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the frequency trackbar */
    if(( hWndFreqText = CreateWindow( "static", szFrequency,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
                                    BORDER_SPACE_CX + FREQ_TB_CX + TEXT_SPACE_CX,
                                    y + (FREQ_TB_CY - Size.cy)/2,
                                    FREQ_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szFrequency;
        uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
        }

    /* Create the FREQUENCY trackbar */
    if(( hWndFreq = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
                                BORDER_SPACE_CX,
                                y, FREQ_TB_CX, FREQ_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szFrequency;
        uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
        }

    SendMessage( hWndFreq, TBM_SETRANGE, FALSE, MAKELONG( FREQ_MIN, FREQ_MAX ));
    SendMessage( hWndFreq, TBM_SETPOS, TRUE, FREQ_MAX );
    SendMessage( hWndFreq, TBM_SETPAGESIZE, 0L, FREQ_PAGESIZE );

    y += FREQ_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the progress trackbar */
    if(( hWndProgText = CreateWindow( "static", szProgress,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
                                    BORDER_SPACE_CX + PROG_TB_CX + TEXT_SPACE_CX,
                                    y + (PROG_TB_CY - Size.cy)/2,
                                    PROG_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szProgress;
        uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
        }

    /* Create the PROGRESSS trackbar */
    if(( hWndProg = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ
                                | TBS_BOTTOM | WS_DISABLED,
                                BORDER_SPACE_CX,
                                y, PROG_TB_CX, PROG_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szProgress;
        uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
        }

    SendMessage( hWndProg, TBM_SETRANGE,
                        FALSE, MAKELPARAM( PROG_MIN, PROG_MAX ));
    SendMessage( hWndProg, TBM_SETPOS, TRUE, 0L );

    x = BORDER_SPACE_CX + PAN_TEXT_CX + TEXT_SPACE_CX
                + PAN_TB_CX + CONTROL_SPACE_CX;
    y += PROG_TB_CY;
    y -= 2*(BUTTON_CY + BUTTON_SPACE_CY) + CHECK_CY;

    /* Create the LOOPED CHECKBOX */
    LoadString( hInst, IDS_CHECK_LOOPED, szTemp, sizeof(szTemp));
    if(( hWndLoopCheck = CreateWindow( "button", szTemp,
                                WS_CHILD | WS_VISIBLE | BS_CHECKBOX,
                                x, y, CHECK_CX, CHECK_CY, hWndMain,
                                (HMENU)IDC_LOOPCHECK, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
        uType = IDS_ERROR_CHECK;
        goto DISPLAY_CREATE_FAILURE;
        }
    y += CHECK_CY + BUTTON_SPACE_CY;

    /* Create the PLAY BUTTON */
    LoadString( hInst, IDS_BUTTON_PLAY, szTemp, sizeof(szTemp));
    if(( hWndPlay = CreateWindow( "button", szTemp,
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
                                    (HMENU)IDM_PLAY, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
        uType = IDS_ERROR_BUTTON;
        goto DISPLAY_CREATE_FAILURE;
        }
    y += BUTTON_CY + BUTTON_SPACE_CY;

    /* Create the STOP BUTTON */
    LoadString( hInst, IDS_BUTTON_STOP, szTemp, sizeof(szTemp));
    if(( hWndStop = CreateWindow( "button", szTemp,
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
                                    (HMENU)IDM_STOP, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
        uType = IDS_ERROR_BUTTON;
        goto DISPLAY_CREATE_FAILURE;
        }

    UpdateFromControls();
    goto RETURN_NORMAL;

DISPLAY_CREATE_FAILURE:
    LoadString( hInst, uType, szType, sizeof(szType));
    wsprintf( szTemp, szTemplate, lpszControl, szType );
    MessageBox( GetActiveWindow(), szTemp,
                        szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    return( TRUE );

RETURN_NORMAL:
    return( FALSE );
    }


/********************************************************************************/
/* UpdateFromControls()                                                         */
/*                                                                              */
/*    This function gets all the required values from the DirectSoundBuffer and */
/* updates the screen interface controls.                                       */
/*                                                                              */
/********************************************************************************/
void UpdateFromControls( void )
    {
    long        lPan, lVol, lFreq;
    HRESULT hr;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    lFreq = (LONG)SendMessage( hWndFreq, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    /* Set the volume and then the pan */
    if( lpDSBStreamBuffer )
        {
        /* Set the volume */
        wsprintf( szTemp, "%s: %lidB", szVolume,
                                            ( lVol + VOL_SHIFT ) / VOL_DIV );
        Static_SetText( hWndVolText, szTemp );

        hr = lpDSBStreamBuffer->lpVtbl->SetVolume( lpDSBStreamBuffer,
                                            (((lVol+VOL_SHIFT) * VOL_MULT)) );
        if( hr != 0 )
            DPF( 0, "Unable to SetVolume in UpdateFromControls()" );
        else
            {
            wsprintf( szDebug, "Set volume to %lidB",
                                            ( lVol + VOL_SHIFT ) / VOL_DIV );
            DPF( 3, szDebug );
            }

        /* Set the Pan */
        wsprintf( szTemp, "%s: %lidB", szPan, ( lPan + PAN_SHIFT ) / PAN_DIV );
        Static_SetText( hWndPanText, szTemp );

        hr = lpDSBStreamBuffer->lpVtbl->SetPan( lpDSBStreamBuffer,
                                            (((lPan+PAN_SHIFT) * PAN_MULT)) );
        if( hr != 0 )
            DPF( 0, "Unable to SetPan in UpdateFromControls()" );
        else
            {
            wsprintf( szDebug, "Set pan to %lidB",
                                            ( lPan + PAN_SHIFT ) / PAN_DIV );
            DPF( 3, szDebug );
            }

        /* Set the frequency */
        wsprintf( szTemp, "%s: %liHz", szFrequency, lFreq * FREQ_MULTIPLIER );
        Static_SetText( hWndFreqText, szTemp );

        hr = lpDSBStreamBuffer->lpVtbl->SetFrequency( lpDSBStreamBuffer,
                                                        lFreq * FREQ_MULTIPLIER);
        if( hr != 0 )
            DPF( 0, "Unable to SetFrequency in UpdateFromControls()" );
        else
            {
            wsprintf( szDebug, "Set frequency to %liHz", lFreq*FREQ_MULTIPLIER );
            DPF( 3, szDebug );
            }
        }
        return;
    }


/********************************************************************************/
/* HandlePanScroll()                                                            */
/*                                                                              */
/*   Handles the pan trackbar scroll when a WM_HSCROLL is received.             */
/*                                                                              */
/********************************************************************************/
void HandlePanScroll( int nCode, int nPos )
    {
    long  lPan, lDelta;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEUP:
            if( lPan >= PAN_MIN - 1 )
                lDelta = -1;
            break;
        case TB_LINEDOWN:
            if( lPan <= PAN_MAX + 1 )
                lDelta = 1;
            break;
        case TB_PAGEUP:
            if( lPan >= PAN_MIN - PAN_PAGESIZE )
                lDelta = -16;
            break;
        case TB_PAGEDOWN:
            if( lPan <= PAN_MAX + PAN_PAGESIZE )
                lDelta = 16;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndPan, TBM_SETPOS, TRUE, lPan + lDelta );
    else
        SendMessage( hWndPan, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }


/********************************************************************************/
/* HandleVolScroll()                                                            */
/*                                                                              */
/*   Handles the volume trackbar scrolling when a WM_HSCROLL is received.       */
/*                                                                              */
/********************************************************************************/
void HandleVolScroll( int nCode, int nPos )
    {
    long  lVol, lDelta;

    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEDOWN:
            if( lVol <= VOL_MAX - 1 )
                lDelta = 1;
            break;
        case TB_LINEUP:
            if( lVol >= VOL_MIN + 1 )
                lDelta = -1;
            break;
        case TB_PAGEDOWN:
            if( lVol <= VOL_MAX - VOL_PAGESIZE )
                lDelta = 10;
            break;
        case TB_PAGEUP:
            if( lVol >= VOL_MIN + VOL_PAGESIZE )
                lDelta = -10;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndVol, TBM_SETPOS, TRUE, (lVol + lDelta));
    else
        SendMessage( hWndVol, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }


/********************************************************************************/
/* HandleFreqScroll()                                                           */
/*                                                                              */
/*   Handles the volume trackbar scrolling when a WM_HSCROLL is received.       */
/*                                                                              */
/********************************************************************************/
void HandleFreqScroll( int nCode, int nPos )
    {
    long  lFreq, lDelta;

    lFreq = (LONG)SendMessage( hWndFreq, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEDOWN:
            if( lFreq <= FREQ_MAX-1 )
                lDelta = 1;
            break;
        case TB_LINEUP:
            if( lFreq >= FREQ_MIN+1 )
                lDelta = -1;
            break;
        case TB_PAGEDOWN:
            if( lFreq <= FREQ_MAX - FREQ_PAGESIZE )
                lDelta = 10;
            break;
        case TB_PAGEUP:
            if( lFreq >= FREQ_MIN + FREQ_PAGESIZE )
                lDelta = -10;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndFreq, TBM_SETPOS, TRUE, (lFreq + lDelta));
    else
        SendMessage( hWndFreq, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }


/****************************************************************************/
/* ErrorMessageBox()                                                        */
/*                                                                          */
/*   A little routine to load error messages from the string resource table */
/* and pop them up in a MessageBox() for the world to see. The dwMBFlags    */
/* parameter allows the caller to specify the type of icon to use.          */
/*                                                                          */
/****************************************************************************/
void ErrorMessageBox( UINT uID, DWORD dwMBFlags )
    {
    LoadString( hInst, uID, szTemp, sizeof(szTemp));
    MessageBox( GetActiveWindow(), szTemp, szAppTitle, MB_OK | dwMBFlags );
    }


/****************************************************************************/
/* BuildTitleBar()                                                          */
/*                                                                          */
/*   Small routine to build and set the title bar text depending on whether */
/* or not a file is open.                                                   */
/****************************************************************************/
void BuildTitleBarText( void )
    {
    char szTitle[ sizeof(szAppCaption) + MAX_PATH + sizeof(" - ")];

    lstrcpy( szTitle, szAppCaption );
    if( bFileOpen )
        {
        lstrcat( szTitle, " - " );
        lstrcat( szTitle, szFileTitle );
        }
    SetWindowText( hWndMain, szTitle );
    }


/****************************************************************************/
/* GetMediaStartPath()                                                      */
/*                                                                          */
/*   This helper function attempts to get the media directory for Direct3D, */
/* which is where all the installed DX wave files go. If it can't find that */
/* it settles for the media sub-directory of the Windows directory.         */
/****************************************************************************/
void GetMediaStartPath( void )
    {
    HKEY    hReg;
    DWORD   cbStartPathLen;

    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                        gszRegKeyDirect3D,
                        0, KEY_READ, &hReg ))
        {
        goto REG_OPEN_FAILED;
        }
    else
        {
        // Query the Registry for the path to the media directory
        cbStartPathLen = sizeof( szCDStartPath );
        if( ERROR_SUCCESS != RegQueryValueEx( hReg, gszRegValueD3DPath,
                                NULL, NULL,
                                szCDStartPath, &cbStartPathLen ))
            {
            goto REG_OPEN_FAILED;
            }
        RegCloseKey( hReg );
        hReg = NULL;
        }

    return;

REG_OPEN_FAILED:
    // Start off by getting the Windows directory -- we're trying to build a
    // file path like "C:\WINDOWS\MEDIA", but the WINDOWS directory could be
    // named anything, so we must ask.
    GetWindowsDirectory( szCDStartPath, sizeof(szCDStartPath));
    // If there's no trailing backslash, append one
    if( lstrcmp( &szCDStartPath[lstrlen(szCDStartPath)], TEXT("\\") ))
        lstrcat( szCDStartPath, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( szCDStartPath, TEXT("MEDIA"));
    }


void LoadFromCommandLine( LPSTR lpszCmd )
    {
    LPSTR lpsz = lpszCmd;
    LPSTR lpToken;
    BOOL  fStartPlaying = FALSE, fStartLooping = FALSE;
    char  szToken[MAX_TOKEN_LEN+1];
    int   i;

    if( !lpsz )
        return;

    // Clear leading spaces
    while( *lpsz == ' ' )
        lpsz++;
    
    // If we need to accept more command-line parameters later, we can
    // extend the code below into a loop that searchs for each one.
    while( *lpsz == '/' || *lpsz == '-' )
        {
        // Don't advance lpsz until we're sure we really should be reading
        // this string (i.e. we recognize that it's the play command
        lpToken = ++lpsz;
        for( i = 0; i < MAX_TOKEN_LEN; i++ )
            {
            if( !*lpToken || *lpToken == ' ' )
                break;
            szToken[i] = *lpToken++;
            }
        szToken[i] = 0;

        if( !lstrcmpi( szToken, gszPlayToken ))
            {
            // Automagically start playing the file
            fStartPlaying = TRUE;
            lpsz = lpToken;
            }
        else if( !lstrcmpi( szToken, gszLoopToken ))
            {
            // Set the player in looping mode at startup
            fStartLooping = TRUE;
            lpsz = lpToken;
            }
        else if( !lstrcmpi( szToken, gszStickyToken ))
            {
            // Use Sticky Focus for the buffer
            gdwFocus = DSBCAPS_STICKYFOCUS;
            lpsz = lpToken;
            }
        else if( !lstrcmpi( szToken, gszGlobalToken ))
            {
            // Use Global Focus for the buffer
            gdwFocus = DSBCAPS_GLOBALFOCUS;
            lpsz = lpToken;
            }
        else if( !lstrcmpi( szToken, gszCloseToken ))
            {
            // "/close" will cause the program to shutdown after it's done
            // playing the file that was presumably loaded at the command-line
            gfCloseOnDone = TRUE;
            lpsz = lpToken;
            }
        else
            {
            // Unrecognized parameter followed the slash, so skip over it
            // and find the next break
            while( *lpsz && *lpsz != ' ' )
                lpsz++;
            }
        // Clear any spaces out again
        while( *lpsz == ' ' )
            lpsz++;
        }

    // If that's all that was on the command-line, simply return
    if( !*lpsz )
        return;

    // ASSUMPTION: We assume that a single filename is the only remaining
    // parameter.  This works out okay because anything else will fail in the
    // file load inside StreamBufferSetup();
    lstrcpy( szFileBuffer, lpsz );

    // Search backwards and find the last backslash, stopping at the
    // beginning of the file name
    lpsz = &szFileBuffer[lstrlen(szFileBuffer)];

    while( lpsz > szFileBuffer )
        {
        if( *(lpsz-1) == '\\' )
            {
            break;
            }
        lpsz--;
        }
    // Fake the szFileTitle, which normally gets set by the Common Dialog
    lstrcpy( szFileTitle, lpsz );
    lstrcpy( szCDStartPath, szFileBuffer );
    szCDStartPath[lpsz-szFileBuffer] = 0;

    if( fStartLooping )
        {
        // Allowing auto-close when the user will have plenty of time to click
        // stop would cause the app to shutdown right as they hit the button,
        // which is weird behavior.
        gfCloseOnDone = FALSE;
        Button_SetCheck( hWndLoopCheck, TRUE );
        }

    if( StreamBufferSetup() != 0 )
        {
        // Error opening the WAVE file so abort
        return;
        }
    else
        {
        bFileOpen = TRUE;
        EnableMenuItem( GetMenu( hWndMain ), IDM_PLAY,
                        MF_BYCOMMAND | MF_ENABLED );
        EnableWindow( hWndPlay, TRUE );
        EnableMenuItem( GetMenu( hWndMain ), IDM_FILE_CLOSE,
                        MF_BYCOMMAND | MF_ENABLED );
        DrawMenuBar( hWndMain );
        BuildTitleBarText();

        if( fStartPlaying )
            SendMessage( hWndMain, WM_COMMAND, MAKEWPARAM( IDM_PLAY, 0 ), 0L );
        }
    }

